using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;

namespace net.notdot.Attercop
{
    public class MessageReader : BinaryReader
    {
        private class PositionStream : Stream
        {
            private Stream baseStream;
            private long pos = 0;

            public PositionStream(Stream s) {
                if (s.CanSeek)
                    throw new ArgumentException("Stream must not be seekable.", "s");

                baseStream = s;
            }

            public override int EndRead(IAsyncResult asyncResult)
            {
                int ret = baseStream.EndRead(asyncResult);
                pos += ret;
                return ret;
            }

            public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
            {
                pos += count;
                return base.BeginWrite(buffer, offset, count, callback, state);
            }

            public override int Read(byte[] buffer, int offset, int count)
            {
                int ret = baseStream.Read(buffer, offset, count);
                pos += ret;
                return ret;
            }

            public override int ReadByte()
            {
                int ret = baseStream.ReadByte();
                pos++;
                return ret;
            }

            public override void Write(byte[] buffer, int offset, int count)
            {
                baseStream.Write(buffer, offset, count);
                pos += count;
            }

            public override void WriteByte(byte value)
            {
                baseStream.WriteByte(value);
                pos++;
            }

            public override long Position
            {
                get
                {
                    return pos;
                }
                set
                {
                    throw new InvalidOperationException("Cannot seek on this stream");
                }
            }

            public override bool CanRead
            {
                get { return baseStream.CanRead; }
            }

            public override bool CanSeek
            {
                get { return baseStream.CanSeek; }
            }

            public override bool CanWrite
            {
                get { return baseStream.CanWrite; }
            }

            public override void Flush()
            {
                baseStream.Flush();
            }

            public override long Length
            {
                get { return baseStream.Length; }
            }

            public override long Seek(long offset, SeekOrigin origin)
            {
                return baseStream.Seek(offset, origin);
            }

            public override void SetLength(long value)
            {
                baseStream.SetLength(value);
            }
        }

        private Stack<long> regionPointers = new Stack<long>();

        public MessageReader(Stream input) : base(input.CanSeek?input:new PositionStream(input)) { }

        public MessageReader(Stream input, Encoding encoding) : base(input.CanSeek?input:new PositionStream(input), encoding) { }

        public new int Read7BitEncodedInt()
        {
            return base.Read7BitEncodedInt();
        }

        public byte[] ReadBytes()
        {
            int length = Read7BitEncodedInt();
            return ReadBytes(length);
        }

        public void BeginRegion()
        {
            long startPos = BaseStream.Position;
            regionPointers.Push(startPos + ReadInt32());
        }

        public void EndRegion()
        {
            if (regionPointers.Count == 0)
                throw new InvalidOperationException("Attempted to end a region when no region is on the stack.");

            Int32 endPos = (Int32) regionPointers.Pop();
            if (endPos < BaseStream.Position)
            {
                throw new InvalidOperationException("Already past the end of the region being ended.");
            }
            else if (endPos > BaseStream.Position)
            {
                if (BaseStream.CanSeek)
                {
                    BaseStream.Seek(endPos, SeekOrigin.Begin);
                }
                else
                {
                    ReadBytes((int)(endPos - BaseStream.Position));
                }
            }
        }
    }

    public class MessageWriter : BinaryWriter
    {
        private Stack<long> regionPointers = new Stack<long>();

        public MessageWriter() : base() { }
        public MessageWriter(Stream stream)
            : base(stream)
        {
            if (!stream.CanSeek)
                throw new ArgumentException("Stream must be seekable", "stream");
        }

        public MessageWriter(Stream stream, Encoding encoding)
            : base(stream, encoding)
        {
            if (!stream.CanSeek)
                throw new ArgumentException("Stream must be seekable", "stream");
        }

        public new void Write7BitEncodedInt(int value)
        {
            base.Write7BitEncodedInt(value);
        }

        public new void Write(byte[] arr)
        {
            Write7BitEncodedInt(arr.Length);
            base.Write(arr);
        }

        public void BeginRegion()
        {
            regionPointers.Push(BaseStream.Position);
            Write((Int32)0);
        }

        public void EndRegion()
        {
            if (regionPointers.Count == 0)
                throw new InvalidOperationException("Attempted to end a region when no region is on the stack.");

            long currentPos = BaseStream.Position;
            long beginPos = regionPointers.Pop();

            BaseStream.Seek(beginPos, SeekOrigin.Begin);
            Write((Int32)(currentPos - beginPos));
            BaseStream.Seek(currentPos, SeekOrigin.Begin);
        }
    }
}
